[Design Pattern] Command

[디자인 패턴][행위 패턴] 커맨드

Posted by ChaelinJ on May 01, 2021

Command

요청을 객체의 형태로 캡슐화하여 사용자가 보낸 요청을 나중에 이용할 수 있도록 메서드 이름, 매개변수 등 요청에 필요한 정보를 저장 또는 로깅, 취소할 수 있게하는 패턴

  • 커맨드 객체는 일련의 행동을 특정 리시버하고 연결시킴으로써 요구 사항을 캡슐화한 것입니다.
  • 이렇게 하기 위해서 행동과 리시버를 한 객체에 집어넣고, execute()라는 메소드 하나만 외부에 공개하는 방법을 씁니다.
  • 이 메소드의 호출에 의해서 리시버에서 일련의 작업이 처리됩니다.
    • 외부에서 볼 땐 어떤 객체가 리시버 역할을 하는지, 그 리시버에서 실제로 어떤 일을 하는지는 알 수 없습니다.

다이어그램

  • Command(명령): Receiver의 정보가 담겨져 있는 객체
    • 실행될 기능을 execute 메서드로 선언합니다.
    • Receiver 객체를 가지고 있고 Receiver의 메서드를 호출합니다.
    • Invoker 객체에 전달됩니다.
  • Receiver(수신자): 행동을 담당
    • ConcreteCommand에서 execute 메서드를 구현할 때 필요한 클래스
  • Invoker(발동자): 커맨드를 저장해 어떤 수행을 할 것인지 결정
    • 필요에 따라 명령 발동에 대한 기록을 남길 수 있습니다.
    • 전달된 커맨드 객체를 받아 명령을 발동합니다.
  • Client(클라이언트): 어느 시점에 어떤 명령을 수행할지 결정
    • 발동자 객체로 커맨드 객체를 전달합니다.
    • 하나 이상의 발동자 객체와 커맨드 객체를 보유합니다.

사용 이유

  • 실행될 기능을 캡슐화함으로써 주어진 여러 기능을 실행할 수 있는 재사용성이 높은 클래스를 설계하는 패턴입니다.
  • 실행될 기능을 캡슐화함으로써 기능의 실행을 요구하는 Invoker 클래스와 실제 기능을 실행하는 Receiver 클래스 사이의 의존성을 제거합니다.

따라서 실행될 기능의 변경에도 Invoker 클래스를 수정없이 그대로 사용할 수 있도록 해줍니다.

예시와 이해

클라이언트에서 스위치를 통해 다양한 수행을 하는 경우를 예제로 만들어 보았습니다.

  • 스위치: Invoker(발동자) - 커맨드 객체를 받아 실행
  • Monitor, Speaker: Receiver(수신자)
  • 커맨드: Receiver 객체를 가지고 있으며, Receiver 객체의 메서드를 호출합니다.

Command & ConcreteCommand

Invoker

Invoker에선 command 실행마다 로그를 기록하도록 static 메소드와 변수를 추가했습니다.

Receiver

Client

위에서 설명한대로 client에서 Invoker에 커맨드를 전달합니다.

출력

turn on Monitor
turn on Speaker
log------------
MonitorCommand@a09ee92
SpeakerCommand@30f39991

Switch에서 로그를 기록하도록하여 수행한 커맨드에 대한 기록을 볼 수 있습니다.

이는 요청을 객체로 취급하는 커맨드 패턴을 활용해 수행할 수 있는 기능 중 하나입니다.

Receiver인 Monitor, Speaker에서 기능 수행 부분인 action() 메소드의 내용을 변경하여도 Invoker인 Switch의 내용을 변경할 필요가 없습니다.

또 커맨드를 추가할 때도 기존 코드를 변경하지 않고 클래스를 추가하는 방식으로 새로운 커맨드를 쉽게 추가할 수 있습니다.

Invoker에서 로그나 Undo 기능을 추가할 수 있습니다. 이를 응용해 트랜잭션의 특성(일관성과 원자성, 독립성, 지속성)을 만족하는 시스템을 작성할 수도 있습니다.

하지만 각각의 커맨드에 대한 클래스를 생성해주어야 하기 때문에 너무 많은 클래스가 생성될 수도 있습니다.

정리

끝으로 강조하자면 커맨드 패턴의 핵심은 캡슐화입니다.

Receiver(수신자)가 무슨 동작을 하는지 무엇을 수행해야 하는지 알 필요 없이 단순히 Invoker(발동자)를 통해 실행시키면 됩니다.

요청을 캡슐화하므로써 로그나 Undo의 기능을 활용할 수도 있습니다.


참조

감사합니다.

Text by Chaelin. Photographs by Chaelin, Unsplash.